/*
 * Allwinner SoCs display driver.
 *
 * Copyright (C) 2016 Allwinner.
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

/*
Description: awf waveform file decoder
Version	: v0.1
Author	: oujunxi
Date	: 2015/07/10
*/

#include "disp_waveform.h"

#define DEBUG_WAVEFILE
#ifdef DEBUG_WAVEFILE
#define WF_DBG(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_INFO(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_WRN(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_ERR(msg, fmt...)		printk(KERN_ERR msg, ##fmt)
#else
#define WF_DBG(msg, fmt...)
#define WF_INFO(msg, fmt...)
#define WF_WRN(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_ERR(msg, fmt...)		printk(KERN_ERR msg, ##fmt)
#endif


#define    C_HEADER_INFO_OFFSET		0
#define    C_HEADER_TYPE_ID_OFFSET	0		/* eink type(eink id) */
#define    C_HEADER_VERSION_STR_OFFSET	1
#define    C_HEADER_INFO_SIZE	128		/* size of awf file header */
#define    C_TEMP_TBL_OFFSET	(C_HEADER_INFO_OFFSET+C_HEADER_INFO_SIZE)
/* temperature table offset from the beginning of the awf file */
#define    C_TEMP_TBL_SIZE		32	/* temperature table size */

#define    C_MODE_ADDR_TBL_OFFSET (C_TEMP_TBL_OFFSET+C_TEMP_TBL_SIZE)
/* mode address table offset */
#define    C_MODE_ADDR_TBL_SIZE	 64
#define    C_INIT_MODE_ADDR_OFFSET			C_MODE_ADDR_TBL_OFFSET
#define    C_GC16_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+4)
#define    C_GC4_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+8)
#define    C_DU_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+12)
#define    C_A2_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+16)
#define    C_GC16_LOCAL_MODE_ADDR_OFFSET	(C_MODE_ADDR_TBL_OFFSET+20)
#define    C_GC4_LOCAL_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+24)
#define    C_A2_IN_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+28)
#define    C_A2_OUT_MODE_ADDR_OFFSET		(C_MODE_ADDR_TBL_OFFSET+32)

#define    C_INIT_MODE_OFFSET	(C_MODE_ADDR_TBL_OFFSET+C_MODE_ADDR_TBL_SIZE)

#define    C_REAL_TEMP_AREA_NUM	 15          /* max temperature range number */
#define    WF_MAX_COL		256		/* GC16, 16*16 = 256 */


typedef struct {
	u8 load_flag;		/* when awf has been loaded, init_flag = 1 */
	char *p_wf_vaddr;	/* virtual address of waveform file */
	u32 p_wf_paddr;		/* phy address of waveform file */
	EINK_PANEL_TYPE eink_panel_type;/*eink type, example PVI, OED and etc*/
	u8 wf_temp_area_tbl[C_TEMP_TBL_SIZE];	/* temperature table */

	/*phy address*/
	u32 p_init_wf;		/* init mode address (include temperature table address) */
	u32 p_gc16_wf;		/* gc16 mode address (include temperature table address) */
	u32 p_gc4_wf;		/* gc4 mode address (include temperature table address) */
	u32 p_du_wf;		/* du mode address (include temperature table address) */
	u32 p_A2_wf;		/* A2 mode address (include temperature table address) */
	u32 p_gc16_local_wf;	/* gc16 local mode address (include temperature table address) */
	u32 p_gc4_local_wf;	/* gc4 local mode address (include temperature table address) */
	u32 p_A2_in_wf;		/* A2 in mode address (include temperature table address) */
	u32 p_A2_out_wf;	/* A2 out mode address (include temperature table address) */
} AWF_WAVEFILE;

static AWF_WAVEFILE g_waveform_file;

/*
*Description	: get temperature range index from temperature table,
*		  if temperature match TBL[id] <= temperature < TBL[id + 1]
*		  condition, then temp range index = id
*Input		: temperature -- temperature of eink panel
*Output		: None
*Return		: 0 & positive -- temperature range index, negative -- fail
*/
static __s32 get_temp_range_index(int temperature)
{
	__s32 index = -EINVAL;

	for (index = 0; index < C_TEMP_TBL_SIZE; index++) {
		if ((g_waveform_file.wf_temp_area_tbl[index] == 0) &&
								(index > 0))
			break;

		if (temperature < g_waveform_file.wf_temp_area_tbl[index])
			break;

	}

	if (index > 0)
		index -= 1;

	return index;
}

/*
Description	: get mode address according to flush mode
Input		: mode -- flush mode
Output		: None
Return		: NULL -- get mode address fail,
others -- get mode address successfully
*/
static u8 *get_mode_virt_address(enum eink_update_mode mode)
{
	u8 *p_wf_file = NULL;
	u32 offset = 0;

	switch (mode) {
	case EINK_INIT_MODE:
	{
		offset = (u32)g_waveform_file.p_init_wf -
						g_waveform_file.p_wf_paddr;
		p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
		break;
	}

	case EINK_DU_MODE:
	case EINK_DU_RECT_MODE:
	{
		offset = (u32)g_waveform_file.p_du_wf -
						g_waveform_file.p_wf_paddr;
		p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
		break;
	}

	case EINK_GC16_MODE:
	case EINK_GC16_RECT_MODE:
	{
		offset = (u32)g_waveform_file.p_gc16_wf -
			g_waveform_file.p_wf_paddr;
		p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
		break;
	}

	case EINK_A2_MODE:
	case EINK_A2_RECT_MODE:
	{
		offset = (u32)g_waveform_file.p_A2_wf -
			g_waveform_file.p_wf_paddr;
		p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
		break;
	}

	case EINK_GC16_LOCAL_MODE:
	case EINK_GC16_LOCAL_RECT_MODE:
	{
		offset = (u32)g_waveform_file.p_gc16_local_wf -
			g_waveform_file.p_wf_paddr;
		p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
		break;
	}

	default:
	{
		WF_WRN("unkown mode(0x%x)\n", mode);
		p_wf_file = NULL;
		break;
	}
	}

	return p_wf_file;
}

/*
*Description	: get mode address according to flush mode
*Input		: mode -- flush mode
*Output		: None
*Return		: NULL -- get mode address fail,
*	          others -- get mode address successfully
*/
static u32 get_mode_phy_address(enum eink_update_mode mode)
{
	u32 phy_addr = 0;

	switch (mode) {
	case EINK_INIT_MODE:
	{
		phy_addr = g_waveform_file.p_init_wf;
		break;
	}

	case EINK_DU_MODE:
	case EINK_DU_RECT_MODE:
	{
		phy_addr = g_waveform_file.p_du_wf;
		break;
	}

	case EINK_GC16_MODE:
	case EINK_GC16_RECT_MODE:
	{
		phy_addr = g_waveform_file.p_gc16_wf;
		break;
	}

	case EINK_A2_MODE:
	case EINK_A2_RECT_MODE:
	{
		phy_addr = g_waveform_file.p_A2_wf;
		break;
	}

	case EINK_GC16_LOCAL_MODE:
	case EINK_GC16_LOCAL_RECT_MODE:
	{
		phy_addr = g_waveform_file.p_gc16_local_wf;
		break;
	}

	default:
	{
		WF_WRN("unkown mode(0x%x)\n", mode);
		phy_addr = 0;
		break;
	}
	}

	return phy_addr;
}


/*
Description	: load waveform file to memory, and save each flush mode
		  of address. This function should be called before waveform
		  data has been used.
Input		: path -- path of waveform file
Output		: None
Return		: 0 -- load waveform ok, others -- fail
*/
extern void *disp_malloc(u32 num_bytes, void *phy_addr);

__s32 init_waveform(const char *path)
{
	struct file *fp = NULL;
	__s32 file_len = 0;
	__s32 read_len = 0;
	mm_segment_t fs;
	loff_t pos;
	__u32 wf_buffer_len = 0;           /* the len of waveform file */
	u32 *pAddr = NULL;
	__s32 ret = -EINVAL;

	if (path == NULL) {
		WF_ERR("path is null\n");
		return -EINVAL;
	}

	WF_DBG("starting to load awf waveform file(%s)\n", path);
	fp = filp_open(path, O_RDONLY, 0);
	if (IS_ERR(fp))	{
		WF_ERR("fail to open waveform file(%s)", path);
		return -EBADF;
	}

	memset(&g_waveform_file, 0, sizeof(g_waveform_file));
	fs = get_fs();
	set_fs(KERNEL_DS);
	pos = 0;
	file_len = fp->f_path.dentry->d_inode->i_size;
	wf_buffer_len = file_len+1023;

	g_waveform_file.p_wf_vaddr = (char *)disp_malloc(wf_buffer_len,
					(u32 *)(&g_waveform_file.p_wf_paddr));
	/* waveform vaddr */
	if (g_waveform_file.p_wf_vaddr == NULL) {
		WF_ERR("fail to alloc memory for waveform file\n");
		ret = -ENOMEM;
		goto error;
	}

	read_len = vfs_read(fp, (char *)g_waveform_file.p_wf_vaddr, file_len, &pos);
	if (read_len != file_len) {
		WF_ERR("miss data(read=%d byte, file=%d byte) when read wavefile\n",
							read_len, file_len);
		ret = -EAGAIN;
		goto error;
	}

	g_waveform_file.eink_panel_type = *((u8 *)g_waveform_file.p_wf_vaddr);
	WF_DBG("eink type=0x%x\n", g_waveform_file.eink_panel_type);

	/* starting to load data */
	memcpy(g_waveform_file.wf_temp_area_tbl,
			(g_waveform_file.p_wf_vaddr+C_TEMP_TBL_OFFSET),
			C_TEMP_TBL_SIZE);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_INIT_MODE_ADDR_OFFSET);
	g_waveform_file.p_init_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_GC16_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc16_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_GC4_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc4_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_DU_MODE_ADDR_OFFSET);
	g_waveform_file.p_du_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_A2_MODE_ADDR_OFFSET);
	g_waveform_file.p_A2_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr +
					C_GC16_LOCAL_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc16_local_wf =
				(u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr +
						C_GC4_LOCAL_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc4_local_wf =
				(u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_A2_IN_MODE_ADDR_OFFSET);
	g_waveform_file.p_A2_in_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_A2_OUT_MODE_ADDR_OFFSET);
	g_waveform_file.p_A2_out_wf =
				(u32)(g_waveform_file.p_wf_paddr + *pAddr);

	if (fp) {
		filp_close(fp, NULL);
		set_fs(fs);
	}

	g_waveform_file.load_flag = 1;

	WF_DBG("load waveform file(%s) successfully\n", path);
	return 0;

error:
	if (g_waveform_file.p_wf_vaddr != NULL)   {
		vfree(g_waveform_file.p_wf_vaddr);
		g_waveform_file.p_wf_vaddr = NULL;
	}

	if (fp) {
		filp_close(fp, NULL);
		set_fs(fs);
	}

	return ret;
}

/*
Description: get eink panel type, for example, 3 stands for ED060XC3(4-bit),
					       4 stands for ED060XD4(5-bit)
Input: None
Output: type -- save the type of eink panel
Return: 0 -- get eink panel type successfully, others -- fail
*/
int get_eink_panel_type(EINK_PANEL_TYPE *type)
{
	if (g_waveform_file.load_flag != 1) {
		WF_ERR("waveform hasn't init yet, pls init first\n");
		return -EAGAIN;
	}

	if (type == NULL) {
		WF_ERR("input param is null\n");
		return -EINVAL;
	}

	*type = g_waveform_file.eink_panel_type;
	return 0;
}

/*
Description: get bit number of eink panel, 4-bit stands for 16 grayscale,
					   5-bit stands for 32 grayscale
Input: None
Output: bit_num -- save the bit number of eink panel
Return: 0 -- get eink panel type successfully, others -- fail
*/
int get_eink_panel_bit_num(enum  eink_bit_num *bit_num)
{
	if (g_waveform_file.load_flag != 1) {
		WF_ERR("waveform hasn't init yet, pls init first\n");
		return -EAGAIN;
	}

	if (bit_num == NULL) {
		WF_ERR("input param is null\n");
		return -EINVAL;
	}

	if (g_waveform_file.eink_panel_type == ED060XD4)
		*bit_num = EINK_BIT_5;
	else
		*bit_num = EINK_BIT_4;

	return 0;
}


/*
Description: get waveform data address according to mode and temperature
Input: None
Output: type -- save the type of eink panel
Return: 0 -- get eink panel type successfully, others -- fail
*/
int get_waveform_data(enum eink_update_mode mode, u32 temp,
				u32 *total_frames, u32 *wf_buf)
{
	u32 p_mode_phy_addr = 0;
	u8 *p_mode_virt_addr = NULL;
	u32 mode_temp_offset = 0;
	u8 *p_mode_temp_addr = NULL;
	/* u16 *p_pointer = NULL; */
	u32 temp_range_id = 0;

	if (g_waveform_file.load_flag != 1) {
		WF_ERR("waveform hasn't init yet, pls init first\n");
		return -EAGAIN;
	}

	if ((total_frames == NULL) || (wf_buf == NULL)) {
		WF_ERR("input param is null\n");
		return -EINVAL;
	}

	p_mode_virt_addr = get_mode_virt_address(mode);
	if (p_mode_virt_addr == NULL) {
		WF_ERR("get mode virturl address fail, mode=0x%x\n", mode);
		return -EINVAL;
	}

	p_mode_phy_addr = get_mode_phy_address(mode);
	if (p_mode_phy_addr == 0) {
		WF_ERR("get mode phy address fail, mode=0x%x\n", mode);
		return -EINVAL;
	}

	temp_range_id = get_temp_range_index(temp);
	if (temp_range_id < 0) {
		WF_ERR("get temp range index fail, temp=0x%x\n", temp);
		return -EINVAL;
	}

	mode_temp_offset =  *((u32 *)p_mode_virt_addr + temp_range_id);
	p_mode_temp_addr = (u8 *)p_mode_virt_addr + mode_temp_offset;

	*total_frames = *((u16 *)p_mode_temp_addr);
	mode_temp_offset = mode_temp_offset + 4;
	/* skip total frame(2 Byte) and dividor(2 Byte) */

	*wf_buf = p_mode_phy_addr + mode_temp_offset;

	return 0;
}


/*
Description	: free memory that used by waveform file, after that, waveform
		  data cannot be used until init_waveform function has been
		  called. This function should be called when unload module.
Input		: None
Output		: None
Return		: None
*/
void free_waveform(void)
{
	if (g_waveform_file.p_wf_vaddr != NULL)   {
		vfree(g_waveform_file.p_wf_vaddr);
		g_waveform_file.p_wf_vaddr = NULL;
	}

	g_waveform_file.load_flag = 0;
	return;
}


